package org.bookshare.validator.rule;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.benetech.beans.error.ValidationError;
import org.benetech.beans.error.XMLValidationError;
import org.benetech.collections.ListMap;
import org.benetech.event.EventListener;
import org.benetech.util.FileUtils;
import org.benetech.util.PreviewContext;
import org.benetech.util.ViolationCollatingErrorHandler;
import org.bookshare.document.beans.DocumentComponent;
import org.bookshare.document.beans.DocumentSet;
import org.bookshare.validator.report.ValidationMetadata;
import org.jdom.Document;
import org.jdom.JDOMException;

/**
 * Parse the documents and keep track of any errors.
 *
 * @author Reuben Firmin
 */
public final class XMLDocumentsParseRule implements Rule {

	private static final int PREVIEW_RANGE = 5;

	/**
	 * Default constructor.
	 *
	 */
	public XMLDocumentsParseRule() {
		// empty
	}

	/**
	 * {@inheritDoc}
	 */
	public void evaluate(final ValidationMetadata validationMetadata, final EventListener listener) throws IOException {
		parseAndValidate(DocumentComponent.OPF, listener, validationMetadata);
		parseAndValidate(DocumentComponent.XML, listener, validationMetadata);
	}

	/**
	 * Parse and validate the given type of documents within the documentset.
	 *
	 * @param element The type of documents to parse
	 * @param listener The use feedback class
	 * @param validationMetadata The metadata about the documentset being validated
	 * @throws IOException if file access fails
	 */
	private void parseAndValidate(final DocumentComponent element, final EventListener listener,
	        final ValidationMetadata validationMetadata) throws IOException {
		final ViolationCollatingErrorHandler errorHandler = new ViolationCollatingErrorHandler();
		try {
			validationMetadata.getDocumentSet().createDocuments(element, false);
			mapParseResults(errorHandler.getViolations(), validationMetadata);
		} catch (JDOMException e) {
			validationMetadata.getExceptions().put(element.getDescription(), e);
		}
	}

	/**
	 * Given parsing results, organize them and insert relevant structures into the ValidationMetadata.
	 *
	 * @param xmlDocMap The map of files to parsed documents.
	 * @param parsingErrors The errors encountered while parsing.
	 * @param validationMetadata The metadata object being built.
	 * @throws IOException If access to the files for generating previews failed.
	 */
	private void mapParseResults(final ListMap<File, ValidationError> parsingErrors,
	        final ValidationMetadata validationMetadata) throws IOException {
		final DocumentSet documentSet = validationMetadata.getDocumentSet();
		for (Map.Entry<File, List<ValidationError>> violationMapping : parsingErrors.entrySet()) {
			final Document doc = documentSet.getDocument(violationMapping.getKey());
			for (ValidationError violation : violationMapping.getValue()) {
				validationMetadata.getDocumentViolations().put(doc, violation);
				if (violation instanceof XMLValidationError) {
					addFilePreviewToViolation((XMLValidationError) violation);
				}
			}
		}
	}

	/**
	 * Given an XMLValidationError, a document, and a map of file name -> document, fill in the previewContext property
	 * of the XMLValidationError.
	 *
	 * @param violation The violation to add preview content to
	 * @throws IOException If file access fails
	 */
	private void addFilePreviewToViolation(final XMLValidationError violation) throws IOException {
		final File file = violation.getErrorContext();
		if (file != null && file.exists()) {
			final String[] lines = FileUtils.getLinesFromFile(violation.getLineNumber() - PREVIEW_RANGE,
					violation.getLineNumber() + PREVIEW_RANGE, file);
			final PreviewContext context = new PreviewContext();
			int lineIndex = violation.getLineNumber() - PREVIEW_RANGE + 1;
			for (int i = 0; i < lines.length; i++) {
				context.addLine(lineIndex++, lines[i]);
			}
			context.setMarker(violation.getLineNumber(), violation.getColumnNumber());
			violation.setPreviewContext(context);
		}
	}

}
